package com.tnovoselec.android.lbs.location;

import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.text.TextUtils;
import android.util.Log;

import com.google.android.maps.GeoPoint;

public class MyLocationService {

	private static final int UPDATES_TIME_DIFF = 5 * 60 * 1000; // 5 minutes
	private static final int UPDATES_DISTANCE_DIFF = 500; // 500 m

	private static final Criteria CRITERIA = new Criteria();
	static {
		CRITERIA.setAccuracy(Criteria.ACCURACY_COARSE);
		CRITERIA.setAltitudeRequired(false);
		CRITERIA.setBearingRequired(false);
		CRITERIA.setCostAllowed(true);
		CRITERIA.setSpeedRequired(false);
		CRITERIA.setPowerRequirement(Criteria.POWER_LOW);
	}

	private static final String LOG_NAME = MyLocationService.class.getName();

	@SuppressWarnings("unused")
	private final Context context;
	private final LocationListener locationListener;
	private final LocationManager locationManager;

	private final int updatesTimeDifference;
	private final int updatesDistanceDifference;
	private final Criteria criteria;

	private boolean active = false;

	public MyLocationService(Context context, LocationListener locationListener) {
		this(context, locationListener, MyLocationService.UPDATES_TIME_DIFF, MyLocationService.UPDATES_DISTANCE_DIFF, MyLocationService.CRITERIA);
	}

	public MyLocationService(Context context, LocationListener locationListener, Criteria criteria) {
		this(context, locationListener, MyLocationService.UPDATES_TIME_DIFF, MyLocationService.UPDATES_DISTANCE_DIFF, criteria);
	}

	public MyLocationService(Context context, LocationListener locationListener, int updatesTimeDifference, int updatesDistanceDifference, Criteria criteria) {
		if ((context == null) || (locationListener == null) || (updatesTimeDifference <= 0) || (updatesDistanceDifference <= 0) || (criteria == null)) {
			throw new IllegalArgumentException("Provide all parameters!");
		}
		this.context = context;
		this.locationListener = locationListener;
		locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

		this.updatesTimeDifference = updatesTimeDifference;
		this.updatesDistanceDifference = updatesDistanceDifference;
		this.criteria = criteria;
	}

	public boolean trackMyLocation() {
		return trackMyLocation(true);
	}

	public boolean trackMyLocation(boolean useLastKnownLocation) {
		return trackMyLocation(useLastKnownLocation, locationManager.getBestProvider(criteria, true));
	}
	
	public boolean trackMyLocation(boolean useLastKnownLocation, String provider) {
		if (active) {
			Log.w(MyLocationService.LOG_NAME, "Already started.");
			return true;
		}

		if (TextUtils.isEmpty(provider) || !locationManager.isProviderEnabled(provider)) {
			return false;
		}

		Log.i(MyLocationService.LOG_NAME, "Using \"" + provider + "\" provider.");

		if (useLastKnownLocation) {
			Location l = locationManager.getLastKnownLocation(provider);
			if (l != null) {
				locationListener.onLocationChanged(l);
			}
		}
		locationManager.requestLocationUpdates(provider, updatesTimeDifference, updatesDistanceDifference, locationListener);

		return active = true; 
	}

	public void stopTrackingMyLocation() {
		if (!active) {
			Log.w(MyLocationService.LOG_NAME, "Already stopped.");
			return;
		}

		active = false;
		locationManager.removeUpdates(locationListener);
	}

	public boolean isActive() {
		return active;
	}

	public static GeoPoint getLastKnownGeoPoint(Context context) {
		return MyLocationService.getLastKnownGeoPoint(context, MyLocationService.CRITERIA);
	}

	public static GeoPoint getLastKnownGeoPoint(Context context, Criteria criteria) {
		Location location = MyLocationService.getLastKnownLocation(context, criteria);
		if (location == null) {
			return null;
		}

		int geoLat = (int) (location.getLatitude() * 1E6);
		int geoLng = (int) (location.getLongitude() * 1E6);

		return new GeoPoint(geoLat, geoLng);
	}

	public static Location getLastKnownLocation(Context context) {
		return MyLocationService.getLastKnownLocation(context, MyLocationService.CRITERIA);
	}

	public static Location getLastKnownLocation(Context context, Criteria criteria) {
		LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
		String provider = locationManager.getBestProvider(criteria, true);

		if (TextUtils.isEmpty(provider)) {
			return null;
		}

		return locationManager.getLastKnownLocation(provider);
	}
}
